/*
 * Galaxium Messenger
 * Copyright (C) 2007 Ben Motmans <ben.motmans@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.Text;
using System.Collections.Generic;

namespace Galaxium.Core
{
	public sealed class ByteArray
	{
		private List<byte> _buffer;

		public ByteArray (byte[] data)
		{
			ThrowUtility.ThrowIfNull ("data", data);

			_buffer = new List<byte> (data.Length);
			_buffer.AddRange (data);
		}

		public ByteArray (int capacity)
		{
			ThrowUtility.ThrowIfLessThenOne ("capacity", capacity);

			_buffer = new List<byte> (capacity);
		}

		public ByteArray ()
			: this (128)
		{
		}

		public void Clear ()
		{
			_buffer.Clear ();
		}

		public int Count
		{
			get { return _buffer.Count; }
		}

		public void Append (string str)
		{
			ThrowUtility.ThrowIfNull ("str", str);
			_buffer.AddRange (Encoding.UTF8.GetBytes (str));
		}

		public void Append (Encoding encoding, string str)
		{
			ThrowUtility.ThrowIfNull ("encoding", encoding);
			ThrowUtility.ThrowIfNull ("str", str);

			_buffer.AddRange (encoding.GetBytes (str));
		}

		public void Append (char value)
		{
			_buffer.AddRange (BitConverter.GetBytes (value));
		}

		public void Append (int value)
		{
			_buffer.AddRange (BitConverter.GetBytes (value));
		}

		public void Append (uint value)
		{
			_buffer.AddRange (BitConverter.GetBytes (value));
		}

		public void Append (short value)
		{
			_buffer.AddRange (BitConverter.GetBytes (value));
		}

		public void Append (ushort value)
		{
			_buffer.AddRange (BitConverter.GetBytes (value));
		}

		public void Append (long value)
		{
			_buffer.AddRange (BitConverter.GetBytes (value));
		}

		public void Append (ulong value)
		{
			_buffer.AddRange (BitConverter.GetBytes (value));
		}

		public void Append (bool value)
		{
			_buffer.AddRange (BitConverter.GetBytes (value));
		}

		public void Append (double value)
		{
			_buffer.AddRange (BitConverter.GetBytes (value));
		}

		public void Append (float value)
		{
			_buffer.AddRange (BitConverter.GetBytes (value));
		}

		public void Append (decimal value)
		{
			foreach (int i in Decimal.GetBits (value)) {
				_buffer.AddRange (BitConverter.GetBytes (i));
			}
		}

		public void Append (byte[] value)
		{
			ThrowUtility.ThrowIfNull ("value", value);

			_buffer.AddRange (value);
		}
		
		public string GetString (int start, int length)
		{
			ThrowUtility.ThrowIfNotInRange ("start", start, 0, _buffer.Count - 1);
			ThrowUtility.ThrowIfNotInRange ("length", length, 0, _buffer.Count - start - 1);

			byte[] buffer = GetChunk (start, length);
			return Encoding.UTF8.GetString (buffer);
		}

		public string GetString (int start, int length, Encoding encoding)
		{
			ThrowUtility.ThrowIfNotInRange ("start", start, 0, _buffer.Count - 1);
			ThrowUtility.ThrowIfNotInRange ("length", length, 0, _buffer.Count - start - 1);
			ThrowUtility.ThrowIfNull ("encoding", encoding);

			byte[] buffer = GetChunk (start, length);
			return encoding.GetString (buffer);
		}

		public char GetChar (int start)
		{
			ThrowUtility.ThrowIfNotInRange ("start", start, 0, _buffer.Count - 3);
			return BitConverter.ToChar (GetChunk (start, 2), 0);
		}

		public short GetInt16 (int start)
		{
			ThrowUtility.ThrowIfNotInRange ("start", start, 0, _buffer.Count - 3);
			return BitConverter.ToInt16 (GetChunk (start, 2), 0);
		}

		public int GetInt32 (int start)
		{
			ThrowUtility.ThrowIfNotInRange ("start", start, 0, _buffer.Count - 5);
			return BitConverter.ToInt32 (GetChunk (start, 4), 0);
		}

		public long GetInt64 (int start)
		{
			ThrowUtility.ThrowIfNotInRange ("start", start, 0, _buffer.Count - 9);
			return BitConverter.ToInt64 (GetChunk (start, 8), 0);
		}

		public ushort GetUInt16 (int start)
		{
			ThrowUtility.ThrowIfNotInRange ("start", start, 0, _buffer.Count - 3);
			return BitConverter.ToUInt16 (GetChunk (start, 2), 0);
		}

		public uint GetUInt32 (int start)
		{
			ThrowUtility.ThrowIfNotInRange ("start", start, 0, _buffer.Count - 5);
			return BitConverter.ToUInt32 (GetChunk (start, 4), 0);
		}

		public ulong GetUInt64 (int start)
		{
			ThrowUtility.ThrowIfNotInRange ("start", start, 0, _buffer.Count - 9);
			return BitConverter.ToUInt64 (GetChunk (start, 8), 0);
		}

		public byte GetByte (int start)
		{
			ThrowUtility.ThrowIfNotInRange ("start", start, 0, _buffer.Count - 2);
			return _buffer[start];
		}

		public byte[] GetChunk (int start, int length)
		{
			ThrowUtility.ThrowIfNotInRange ("start", start, 0, _buffer.Count - 1);
			ThrowUtility.ThrowIfNotInRange ("length", length, 0, _buffer.Count - start - 1);

			byte[] chunk = new byte[length];
			_buffer.CopyTo (start, chunk, 0, length);
			return chunk;
		}

		public bool GetBool (int start)
		{
			ThrowUtility.ThrowIfNotInRange ("start", start, 0, _buffer.Count - 2);
			return BitConverter.ToBoolean (new byte[] { _buffer[start] }, 0);
		}

		public double GetDouble (int start)
		{
			ThrowUtility.ThrowIfNotInRange ("start", start, 0, _buffer.Count - 9);
			return BitConverter.ToDouble (GetChunk (start, 8), 0);
		}

		public float GetSingle (int start)
		{
			ThrowUtility.ThrowIfNotInRange ("start", start, 0, _buffer.Count - 5);
			return BitConverter.ToSingle (GetChunk (start, 5), 0);
		}

		public decimal GetDecimal (int start)
		{
			ThrowUtility.ThrowIfNotInRange ("start", start, 0, _buffer.Count - 17);
			int[] chunks = new int[4];
			for (int i = 0; i < 4; i++) {
				chunks[i] = GetInt32 (start + (i * 4));
			}
			return new decimal (chunks);
		}
		
		public void Insert (int index, string str)
		{
			ThrowUtility.ThrowIfNull ("str", str);
			ThrowUtility.ThrowIfNotInRange ("index", index, 0, _buffer.Count - 1);

			_buffer.InsertRange (index, Encoding.UTF8.GetBytes (str));
		}

		public void Insert (int index, Encoding encoding, string str)
		{
			ThrowUtility.ThrowIfNotInRange ("index", index, 0, _buffer.Count - 1);
			ThrowUtility.ThrowIfNull ("encoding", encoding);
			ThrowUtility.ThrowIfNull ("str", str);

			_buffer.InsertRange (index, encoding.GetBytes (str));
		}

		public void Insert (int index, char value)
		{
			ThrowUtility.ThrowIfNotInRange ("index", index, 0, _buffer.Count - 1);
			_buffer.InsertRange (index, BitConverter.GetBytes (value));
		}

		public void Insert (int index, int value)
		{
			ThrowUtility.ThrowIfNotInRange ("index", index, 0, _buffer.Count - 1);
			_buffer.InsertRange (index, BitConverter.GetBytes (value));
		}

		public void Insert (int index, uint value)
		{
			ThrowUtility.ThrowIfNotInRange ("index", index, 0, _buffer.Count - 1);
			_buffer.InsertRange (index, BitConverter.GetBytes (value));
		}

		public void Insert (int index, short value)
		{
			ThrowUtility.ThrowIfNotInRange ("index", index, 0, _buffer.Count - 1);
			_buffer.InsertRange (index, BitConverter.GetBytes (value));
		}

		public void Insert (int index, ushort value)
		{
			ThrowUtility.ThrowIfNotInRange ("index", index, 0, _buffer.Count - 1);
			_buffer.InsertRange (index, BitConverter.GetBytes (value));
		}

		public void Insert (int index, long value)
		{
			ThrowUtility.ThrowIfNotInRange ("index", index, 0, _buffer.Count - 1);
			_buffer.InsertRange (index, BitConverter.GetBytes (value));
		}

		public void Insert (int index, ulong value)
		{
			ThrowUtility.ThrowIfNotInRange ("index", index, 0, _buffer.Count - 1);
			_buffer.InsertRange (index, BitConverter.GetBytes (value));
		}

		public void Insert (int index, bool value)
		{
			ThrowUtility.ThrowIfNotInRange ("index", index, 0, _buffer.Count - 1);
			_buffer.InsertRange (index, BitConverter.GetBytes (value));
		}

		public void Insert (int index, double value)
		{
			ThrowUtility.ThrowIfNotInRange ("index", index, 0, _buffer.Count - 1);
			_buffer.InsertRange (index, BitConverter.GetBytes (value));
		}

		public void Insert (int index, float value)
		{
			ThrowUtility.ThrowIfNotInRange ("index", index, 0, _buffer.Count - 1);
			_buffer.InsertRange (index, BitConverter.GetBytes (value));
		}

		public void Insert (int index, decimal value)
		{
			ThrowUtility.ThrowIfNotInRange ("index", index, 0, _buffer.Count - 1);
			foreach (int i in Decimal.GetBits (value)) {
				_buffer.InsertRange (index, BitConverter.GetBytes (i));
			}
		}

		public void Insert (int index, byte[] value)
		{
			ThrowUtility.ThrowIfNotInRange ("index", index, 0, _buffer.Count - 1);
			ThrowUtility.ThrowIfNull ("value", value);

			_buffer.InsertRange (index, value);
		}
		
		public byte[] GetByteArray ()
		{
			return _buffer.ToArray ();
		}

		public override string ToString ()
		{
			return Encoding.UTF8.GetString (GetByteArray ());
		}
	}
}